package com.google.code.shardbatis.util;

import com.google.code.shardbatis.annotation.JmxShard;
import com.google.code.shardbatis.annotation.SymbolShard;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class SymbolShardValue {
    private SymbolShardValue() {
    }

    public static Object[] getShardValue(Object params, String mapperId) throws Exception {

        if (params != null && StringUtils.isNotBlank(mapperId)) {
            int lastPoint = mapperId.lastIndexOf(".");
            String clazzName = mapperId.substring(0, lastPoint);
            String methodName = mapperId.substring(lastPoint + 1);
            Class clazz = Class.forName(clazzName);
            Method[] methods = clazz.getMethods();

            if (methods.length <= 0) {
                throw new Exception("class has no method!");
            }

            List<Integer> shardFieldIndexes = new ArrayList<Integer>();
            List<Class> shardFieldTypes = new ArrayList<Class>();
            List<SymbolShard> fieldAnnotations = new ArrayList<>();

            for (Method method : methods) {
                if (methodName.equals(method.getName())) {
                    Annotation[][] annotations = method.getParameterAnnotations();
                    if (annotations == null || annotations.length <= 0) {
                        throw new Exception("method has no shard field");
                    }
                    for (int i = 0; i < annotations.length; i++) {
                        Annotation[] fieldAnno = annotations[i];
                        if (fieldAnno != null && fieldAnno.length > 0) {
                            for (Annotation annotation : fieldAnno) {
                                if (annotation.annotationType() == SymbolShard.class) {
                                    shardFieldIndexes.add(i);
                                    shardFieldTypes.add(method.getParameterTypes()[i]);
                                    fieldAnnotations.add((SymbolShard) annotation);
                                }
                            }
                        }
                    }
                }
            }
            if (shardFieldIndexes.size() <= 0) {
                throw new Exception("method has no shard field");
            }

            Object[] values = new Object[shardFieldIndexes.size()];
            for (int i = 0; i < shardFieldIndexes.size(); i++) {
                int shardFieldIndex = shardFieldIndexes.get(i);
                Class shardFieldType = shardFieldTypes.get(i);
                SymbolShard fieldAnnotation = fieldAnnotations.get(i);
                if (params.getClass() == shardFieldType) {
                    values[i] = getFieldValue(fieldAnnotation, params);
                } else {
                    String key = "param" + (shardFieldIndex + 1);
                    HashMap<String, Object> map = (HashMap<String, Object>) params;
                    Object tmp = map.get(key);
                    values[i] = getFieldValue(fieldAnnotation, tmp);
                }
            }

            return values;
        }

        return null;
    }

    private static Object getFieldValue(JmxShard fieldAnnotation, Object params) throws Exception {
        if (isBasicType(params)) {
            return params;
        } else {
            String shardFieldName = fieldAnnotation.value();
            if (StringUtils.isBlank(shardFieldName)) {
                throw new Exception("the shardFieldName was not annotated");
            }
            Field field = null;
            try {
                field = params.getClass().getDeclaredField(shardFieldName);
            } catch (NoSuchFieldException e) {
                field = params.getClass().getSuperclass().getDeclaredField(shardFieldName);
            }
            field.setAccessible(true);
            return field.get(params);
        }
    }

    private static boolean isBasicType(Object param) {
        if (param == null) {
            return false;
        } else if (param instanceof String) {
            return true;
        } else if (param instanceof BigDecimal) {
            return true;
        } else if (param instanceof Integer) {
            return true;
        } else if (param instanceof Long) {
            return true;
        } else if (param instanceof Double) {
            return true;
        } else if (param instanceof Float) {
            return true;
        } else if (param instanceof Character) {
            return true;
        } else if (param instanceof Byte) {
            return true;
        } else if (param instanceof Short) {
            return true;
        } else if (param instanceof Boolean) {
            return true;
        }
        return false;
    }
}
